package edu.hcmus.sow.dao;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.List;

import javax.annotation.Resource;

import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Criterion;
import org.springframework.dao.DataAccessException;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

/**
 * @see edu.hcmus.sow.dao.GenericDAO
 * 
 * @author Tony Phuong
 * 
 */
@Repository
@Transactional
public abstract class AbstractHibernateDAO<T extends Object, ID extends Serializable> implements GenericDAO<T, ID> {

   @Resource
   private SessionFactory sessionFactory;

   private Class<T> persistentClass;

   @SuppressWarnings("unchecked")
   public AbstractHibernateDAO() {
      this.persistentClass = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass())
            .getActualTypeArguments()[0];
   }

   protected Session getSession() {
      return this.sessionFactory.getCurrentSession();
   }

   public T persist(T toPersist) throws DataAccessException {
      getSession().persist(toPersist);
      return toPersist;
   }

   public T merge(T toMerge) throws DataAccessException {
      getSession().merge(toMerge);
      return toMerge;
   }

   public void delete(T toDelete) throws DataAccessException {
      getSession().delete(toDelete);
   }

   public void refresh(T toRefresh) throws DataAccessException {
      getSession().refresh(toRefresh);
   }

   @SuppressWarnings("unchecked")
   public T findById(ID id) throws DataAccessException {
      T entity;
      entity = (T) getSession().get(persistentClass, id);
      return entity;
   }

   public List<T> findAll() throws DataAccessException {
      return findByCriteria();
   }

   @SuppressWarnings("unchecked")
   protected List<T> findByCriteria(Criterion... criterion) throws DataAccessException {
      Criteria crit = getSession().createCriteria(persistentClass);
      for (Criterion c : criterion) {
         crit.add(c);
      }
      return crit.list();
   }

   protected List<T> executeQuery(String queryString) throws DataAccessException {
      return executeQuery(queryString, (Object[]) null);
   }

   protected List<T> executeQuery(String queryString, Integer firstResult, Integer maxResults)
         throws DataAccessException {
      return executeQuery(queryString, firstResult, maxResults, (Object[]) null);
   }

   @SuppressWarnings("unchecked")
   protected List<T> executeQuery(String queryString, Object... parameters) throws DataAccessException {
      Query query = createQuery(queryString, parameters);
      return query.list();
   }

   @SuppressWarnings("unchecked")
   protected List<T> executeQuery(String queryString, Integer firstResult, Integer maxResults, Object... parameters)
         throws DataAccessException {
      Query query = createQuery(queryString, firstResult, maxResults, parameters);
      return query.list();
   }

   protected Query createQuery(String queryString) throws DataAccessException {
      return createQuery(queryString, (Object[]) null);
   }

   protected Query createQuery(String queryString, Object... parameters) throws DataAccessException {
      Query query = getSession().createQuery(queryString);
      if (parameters != null) {
         for (int i = 0; i < parameters.length; i++) {
            query.setParameter(i, parameters[i]);
         }
      }

      return query;
   }

   protected Query createQuery(String queryString, Integer firstResult, Integer maxResults) throws DataAccessException {
      return createQuery(queryString, firstResult, maxResults, (Object[]) null);
   }

   protected Query createQuery(String queryString, Integer firstResult, Integer maxResults, Object... parameters)
         throws DataAccessException {
      Query query = createQuery(queryString, parameters);

      query.setFirstResult(firstResult == null || firstResult < 0 ? 0 : firstResult);
      if (maxResults != null && maxResults > 0)
         query.setMaxResults(maxResults);

      return query;
   }

}
